﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.ComponentModel;
using System.Xml.Linq;
using System.Reflection;

namespace Objects
{
    public partial class Valuehost<T> : Valuehost
    {
        #region Константы
        protected const string ABSORBE_INTERFACE = "IAbsorber`1";
        protected const string COALESCE_INTERFACE = "ICoalescable`1";
        protected const string DECOALESCE_INTERFACE = "IDecoalescable`1";
        protected const string COMPOSITE_INTERFACE = "ICompositable`1";
        protected const string DECOMPOSITE_INTERFACE = "IDecompositable`1";
        protected const string ABSORBE_METHOD = "Absorbe";
        protected const string COALESCE_METHOD = "Coalesce";
        protected const string DECOALESCE_METHOD = "Decoalesce";
        protected const string COMPOSITE_METHOD = "Composite";
        protected const string DECOMPOSITE_METHOD = "Decomposite";
        #endregion

        /// <summary>
        /// 
        /// </summary>
        /// <param name="valueinj"></param>
        /// <param name="dictMethods"></param>
        /// <param name="intfName"></param>
        /// <param name="methName"></param>
        /// <returns>Возвращает false если только не найден метод обработки</returns>
        protected virtual bool SpecOp(ValueInjector valueinj, Dictionary<Type, MethodInfo> dictMethods, string intfName, string methName)
        {
            MethodInfo mi;
            Type vtype;
            bool res = valueinj != null;
            if (res && valueinj.Value != null)
            {
                T value = this.SpecValue;
                vtype = valueinj.Value.GetType();
                // Если текущее значение отлично от default и найден метод для vtype или его предков
                if (res = (!value.IsEquals(default(T))))
                {
                    if (res = (dictMethods.TryGetValue(vtype, out mi)
                        || (mi = dictMethods
                            .Where(s => vtype.IsSubclassOf(s.Key))
                            .Select(s => s.Value)
                            .FirstOrDefault()) != null
                        || (mi = value.GetType().GetInterfaces()
                            .Where(s => s.Name == intfName)
                            .Select(s => s.GetMethod(methName))
                            .FirstOrDefault(s => vtype.IsSubclassOf(s.GetParameters()[0].ParameterType))) != null))
                        this.SpecValue = (T)mi.Invoke(value, new object[] { valueinj.Value });
                }
            }
            return res;
        }

        public override object Absorbe(Valuehost val)
        {
            ValueInjector vi;
            Valuehost vh;
            T res;
            if (!SpecOp(vi = val as ValueInjector, absorbeMethods, ABSORBE_INTERFACE, ABSORBE_METHOD))
            {
                if ((vi!=null && vi.TryCast<T>(out res)) || !ValueInjector.TryCast<T>(val, out res))
                {
                    if (ValueIsHost)
                        if ((vh = SpecValue as Valuehost) != null)
                            vh.Inject(val);
                }
                else
                    _Value = res;
            }
            return this;
        }

        public override object Coalesce(Valuehost val)
        {
            SpecOp(val as ValueInjector, coalesceMethods, COALESCE_INTERFACE, COALESCE_METHOD);
            return this;
        }

        public override object Decoalesce(Valuehost val)
        {
            SpecOp(val as ValueInjector, decoalesceMethods, DECOALESCE_INTERFACE, DECOALESCE_METHOD);
            return this;
        }

        public override object Composite(Valuehost val)
        {
            SpecOp(val as ValueInjector, compositMethods, COMPOSITE_INTERFACE, COMPOSITE_METHOD);
            return this;
        }

        public override object Decomposite(Valuehost val)
        {
            SpecOp(val as ValueInjector, decompositMethods, DECOMPOSITE_INTERFACE, DECOMPOSITE_METHOD);
            return this;
        }

        private static Dictionary<Type, MethodInfo> absorbeMethods = new Dictionary<Type, MethodInfo>();
        private static Dictionary<Type, MethodInfo> coalesceMethods = new Dictionary<Type, MethodInfo>();
        private static Dictionary<Type, MethodInfo> decoalesceMethods = new Dictionary<Type, MethodInfo>();
        private static Dictionary<Type, MethodInfo> compositMethods = new Dictionary<Type, MethodInfo>();
        private static Dictionary<Type, MethodInfo> decompositMethods = new Dictionary<Type, MethodInfo>();

        private static bool ValueIsHost = false;
        /// <summary>
        /// Инъекция
        /// </summary>
        public override bool Inject(Valuehost value)
        {
            bool res = false;
            LazyValue = null;
            ValueInjector vi;
            LazyObject<object> lv;
            res = base.Inject(value);
            if (!res && (res = ((vi = value as ValueInjector) != null))
                    && (lv = vi.Value as LazyObject<object>) != null)
                LazyValue = lv;
            else
                Absorbe(value);
            return res;
        }
        private LazyObject<object> LazyValue;

        static Valuehost()
        {
            Type[] prms = new Type[] 
            { 
                typeof(string), 
                typeof(T).MakeByRefType() 
            };
            typeTryParse = typeof(T)
                .GetMethods()
                .Where(s => (s.Name == "TryParse")
                            && (s.ReturnType == typeof(bool))
                            && s.IsStatic).FirstOrDefault();
            ValueIsHost = typeof(T).IsSubclassOf(typeof(Valuehost));

            foreach (var item in typeof(T).GetInterfaces()
                    .Where(s => s.Name == ABSORBE_INTERFACE)
                    .Select(s => s.GetMethod(ABSORBE_METHOD)))
                absorbeMethods.Add(item.GetParameters()[0].ParameterType, item);

            foreach (var item in typeof(T).GetInterfaces()
                    .Where(s => s.Name == COALESCE_INTERFACE)
                    .Select(s => s.GetMethod(COALESCE_METHOD)))
                coalesceMethods.Add(item.GetParameters()[0].ParameterType, item);

            foreach (var item in typeof(T).GetInterfaces()
                    .Where(s => s.Name == DECOALESCE_INTERFACE)
                    .Select(s => s.GetMethod(DECOALESCE_METHOD)))
                decoalesceMethods.Add(item.GetParameters()[0].ParameterType, item);

            foreach (var item in typeof(T).GetInterfaces()
                    .Where(s => s.Name == COMPOSITE_INTERFACE)
                    .Select(s => s.GetMethod(COMPOSITE_METHOD)))
                compositMethods.Add(item.GetParameters()[0].ParameterType, item);

            foreach (var item in typeof(T).GetInterfaces()
                    .Where(s => s.Name == DECOMPOSITE_INTERFACE)
                    .Select(s => s.GetMethod(DECOMPOSITE_METHOD)))
                decompositMethods.Add(item.GetParameters()[0].ParameterType, item);

            if (!ParseFunc.TryGetValue(typeof(T), out DefaultParseFunc) && typeTryParse != null)
                DefaultParseFunc = (s) =>
                    {
                        object[] res;
                        if ((bool)typeTryParse.Invoke(null, res = new object[] { s, null }))
                            return (T)res[1];
                        else
                            return null;
                    };
        }

        static Func<string, object> DefaultParseFunc;

        public Valuehost()
            : base()
        {
            if (DefaultParseFunc != null)
                parseFunc = DefaultParseFunc;
            else
                parseFunc = notParsed;
        }

        public Valuehost(T Value)
        {
            this.SpecValue = Value;
        }

        private T _Value;
        /// <summary>
        /// 
        /// </summary>        
        public T SpecValue
        {
            get
            {
                if (LazyValue != null)
                {
                    if (LazyValue.IsPermanent)
                        LazyValue = null;
                    Inject(new ValueInjector(LazyValue.GetObject()));
                }
                return _Value;
            }
            set
            {
                LazyValue = null;
                _Value = value;
            }
        }

        public override object Value
        {
            get
            {
                return SpecValue;
            }
            set
            {
                SpecValue = (T)value;
            }
        }

        public static implicit operator T(Valuehost<T> value)
        {
            if (value == null)
                return default(T);
            else
                return value.SpecValue;
        }

        protected override void Clone(ref Valuehost chain)
        {
            Valuehost<T> res;
            chain = res = (chain as Valuehost<T>) ?? new Valuehost<T>();
            ICloneable cl = SpecValue as ICloneable;
            if (cl != null)
                res.SpecValue = (T)cl.Clone();
            else
                res.SpecValue = SpecValue;
        }

        private object notParsed(string val)
        { return SpecValue; }

        private Func<string, object> parseFunc;

        private static MethodInfo typeTryParse;

        public override string ToString()
        {
            if (SpecValue != null)
                return SpecValue.ToString();
            else
                return base.ToString();
        }

        #region IAbsorber<string> Members

        public override object Absorbe(string val)
        {
            //object[] res;
            //if (Value.IsEquals(default(T)) /*|| !Inject(val.InjectValue())*/)
            //{
            //    if (typeTryParse != null)
            //        if ((bool)typeTryParse.Invoke(null, res = new object[] { val, null }))
            //            this.Value = (T)res[1];
            //}
            //else
            this.SpecValue = (T)parseFunc(val);
            return this;
        }

        #endregion
    }

    public static partial class ValuehostExtension
    {
        public static Valuehost InjectValue(this object obj)
        {
            return new ValueInjector(obj);
        }
    }
}
